package com.baroxtech.anoterecorder;

import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import java.util.LinkedList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

public class ANoteRecorder {
    // 录音器状态
    public static class State {
        public static final int STOPPED = 0;
        public static final int RECORDING = 1;
        public static final int PAUSING = 2;
    }

    public interface EventHandler {
        void onEvent(double packTime, final List<ANote> notes);
    }

    // 最小打包间隔时间。单位是秒
    public static final double MIN_PACK_INTERVAL = 0.02;

    private final static String TAG = "ANoteRecorder";
    private long resumeTime;
    private double passedDuration;
    private double packInterval;
    private EventHandler eventHandler;
    private Handler mainHandler;
    private LinkedList<ANote> noteCache;
    private boolean recordNoteOff;
    private int state;
    private TimerTask timerTask;
    private Timer timer;


    public static ANoteRecorder create(double packInterval, EventHandler eventHandler) {
        if (packInterval < MIN_PACK_INTERVAL) {
            return null;
        }
        return new ANoteRecorder(packInterval, eventHandler);
    }

    private ANoteRecorder(double packInterval, EventHandler eventHandler) {
        this.packInterval = packInterval;

        this.state = State.STOPPED;
        noteCache = new LinkedList<>();
        this.eventHandler = eventHandler;
        passedDuration = 0;
        recordNoteOff = true;
        mainHandler = new Handler(Looper.getMainLooper());
    }

    public boolean isRecordNoteOff() {
        return recordNoteOff;
    }

    public void setRecordNoteOff(boolean recordOff) {
        recordNoteOff = recordOff;
    }

    private double getOnset() {
        return passedDuration + ((double) (System.nanoTime() - resumeTime)) / 1e9;
    }

    private double getOnset(long timestamp) {
        return passedDuration + ((double) (timestamp - resumeTime)) / 1e9;
    }

    private double getPackTimeMax(double value) {
        return ((long) (value / packInterval)) * packInterval;
    }

    public int getState() {
        return state;
    }

    public void setState(int state) {
        switch (state) {
            case State.RECORDING:
                if (this.state == state) return;
                this.resumeTime = System.nanoTime();
                if (timer == null) {
                    timerTask = new TimerTask() {
                        @Override
                        public void run() {
                            mainHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    packOnce(false);
                                }
                            });
                        }
                    };
                    timer = new Timer();
                    timer.scheduleAtFixedRate(timerTask, 0, (long) (MIN_PACK_INTERVAL * 1000));
                }
                Log.i(TAG, "resume time = " + resumeTime);
                this.state = state;
                return;
            case State.PAUSING:
                if (this.state != State.RECORDING) return;
                this.state = state;
                this.passedDuration = getOnset();
                if (timer != null) {
                    timer.cancel();
                    timer = null;
                }
                return;
            case State.STOPPED:
                if (this.state == state) return;
                if (timer != null) {
                    timer.cancel();
                    timer = null;
                }
                this.state = state;
                // 处理剩余的音符
                packOnce(true);
                this.passedDuration = 0;
                return;
        }
    }

    public void process(final List<AMIDICommand> commands) {
        if (state != State.RECORDING)
            return;
        // 把 commands 拷贝到缓冲中，执行计算
        ANote note = null;
        byte vel = 0;
        for (AMIDICommand cmd : commands) {
            if (cmd.status != AMIDIConstants.STATUS_NOTE_ON &&
                    cmd.status != AMIDIConstants.STATUS_NOTE_OFF)
                continue;
            vel = cmd.getVelocity();
            // 如果不记录 note off 则直接过滤掉
            if (!recordNoteOff && vel == 0) continue;

            note = new ANote();
            note.velocity = vel;
            note.pitch = cmd.getPitch();
            note.channel = cmd.getChannel();
            note.onset = getOnset(cmd.timestamp);
            Log.i(TAG, "time stamp = " + cmd.timestamp);

            noteCache.add(note);
        }
        packOnce(false);
    }

    private void packOnce(boolean force) {
        if (noteCache.isEmpty())
            return;
        // 求可到达的最大时间点
        double now = force ? Double.MAX_VALUE : getPackTimeMax(getOnset());
        // 切片数据
        ANote note = null;
        LinkedList<ANote> notes = new LinkedList<>();
        double firstPackTimeInThisRange = 0.0;
        double thisPackTime = 0.0;

        while (!noteCache.isEmpty()) {
            note = noteCache.getFirst();
            if (note.onset > now)
                break;
            // 判定当前 note 和本组第一个数据是否在同一组内
            thisPackTime = getPackTimeMax(note.onset);
            if (notes.isEmpty()) {
                firstPackTimeInThisRange = thisPackTime;
            } else if (firstPackTimeInThisRange != thisPackTime) {
                eventHandler.onEvent(firstPackTimeInThisRange, notes);
                // 这属于新的一组数据
                notes = new LinkedList<>();
                firstPackTimeInThisRange = thisPackTime;
            }
            notes.add(note);
            noteCache.removeFirst();
        }
        if (!notes.isEmpty()) {
            eventHandler.onEvent(firstPackTimeInThisRange, notes);
        }
    }
}
